import kotlinx.coroutines.*

class CoroutineTest {


    /**
     * 示例代码,演示四种常用的协程
     * 无返回值协程
     * runBlocking顶层协程
     *      外部是线程,内部就是协程
     *      会阻塞外部线程,所以主要是测试使用
     *  launch 无返回值协程
     *      只能在协程内启动
     *      会返回job外部引用
     *      可以操作job取消
     *      无法获得协程运行结果
     *  可返回值协程
     *  withContext 运行lamda参数,返回lamda结果.
     *      相当于asuc{}.await(),立即调用await等待结果返回
     *  asyc 返回一个Deffered对象,通过Deferred.await()方法获取返回值
     *      await()方法会阻塞当前协程
     */
    fun demoForCoroutines() = runBlocking {

        println("======launch协程演示")
        demoForLaunch()
        println("======withContext协程演示")
        demoForWithContext()
        println("======async协程演示")
        demoForAsync()
    }

    /**
     * 展示函数
     * async 会返回Deferred对象,可以通过Deferred.await()返回lamda结果
     * 多个async是并行执行的
     * 输出结果时,只有所有任务执行完毕,统一输出结果
     * await()可以重复调用
     */
    private suspend fun demoForAsync() {
        withContext(Dispatchers.Default) {
            //启动asyc协程,2s后,返回2000
            val jobAsync1 = async {
                delay(2000)
                println("async task 1")
                2000
            }
            //启动async协程,1s后返回1000
            val jobAsync2 = async {
                delay(1000)
                println("async task 2")
                1000
            }
            //启动async协程,3s后返回3000
            val jobAsync3 = async {
                delay(3000)
                println("async task 3")
                3000
            }
            //启动async协程,3s后返回3000
            val jobAsync4 = async {
                delay(4000)
                println("async task 4")
                4000
            }
            //只有job1和job2都返回,才会统一打印
            println("async task1=${jobAsync1.await()}    async task2=${jobAsync2.await()}")
            //await可以重复调用
            println("async task3=${jobAsync3.await()}    async task2=${jobAsync2.await()}")
            //取消了job4
            jobAsync4.cancelAndJoin()
            //这里再输出job4,就会抛出JobCancellationException
            println("async task4=${jobAsync4.await()}")
        }

    }

    /**
     * 展示函数
     * 启动withContext
     * 可以看到,withContext是串行执行的
     *  withContext 本身是可以在挂起函数中运行的,launch和async不可以单独在挂起函数中执行
     *      可以为launch和async创造协程执行环境
     */
    private suspend fun demoForWithContext() {
        //启动withContext
        //启动一个协程,延迟2s后的返回值2000
        val jobWithContext1 = withContext(Dispatchers.Default) {
            delay(2000)
            println("withContext task 1")
            2000
        }
        //启动协程,1s后返回1000
        val jobWithContext2 = withContext(Dispatchers.Default) {
            delay(1000)
            println("withContext task 2")
            1000
        }
        //输出两个协程的返回值
        println("withContext task1=$jobWithContext1")
        println("withContext task2=$jobWithContext2")
    }

    /**
     * 演示函数
     * 通过launch创建一个协程,返回引用job
     * 在job内部,输出当前线程每隔1s打印一次index
     */
    private suspend fun demoForLaunch() {
        println("=启动了一个协程,输出当前线程每隔1s打印一次index")
        withContext(Dispatchers.Default) {
            val job = launch {
                println("Current Thread : ${Thread.currentThread()}")
                repeat(10) {
                    println("$it")
                    delay(1000)
                }
            }
            println("=输出1300ms后,取消协程任务并结束演示")
            //job外部,挂起5s
            delay(1300)
            //job取消并等待结束
            job.cancelAndJoin()
        }
    }

    /**
     * 演示协程取消
     */
    fun demoCoroutineCancel() = runBlocking {

        val job =
            launch {
                var time = System.currentTimeMillis()
                var i = 0
                while (i < 6) {
                    if (System.currentTimeMillis() >= time) {
                        println("这是第 ${++i} 次")
                        time += 500
                        //中间没有任何协程库函数,默认不会检查协程生命周期,就无法取消本协程
                        //delay(10)
                    }
                }
            }
    }
}